<?php

namespace liketp\database\concern;

use liketp\exception\Exception;
use liketp\facade\Db;

/**
 * 查询器的查询方法
 * 即最后的需要返回结果的那些方法，CURD、dec、inc、buildSql 等
 */

trait ExecuteQuery
{
    use ResultOperate;

    //------------------------------------------------ 执行方法
    public function select(int $limit = 0)
    {
        $limit && $this->limit($limit);
        $result = $this->connector->execute($this, 'select');
        $result = is_array($result) ? $result : [];
        return $this->processResult($result, true);
    }

    public function selectOrFail()
    {
        $result = $this->select();
        if (empty($result)) throw new Exception('Data not found');
        return $this->processResult($result, true);
    }

    public function find($id = null)
    {
        if (!is_null($id) && !empty($this->pk)) $this->where($this->pk, $id);

        $this->limit(1);
        $list = $this->connector->execute($this, 'select');

        if ($this->isFetch()) return $list;
        $result = empty($list) ? null : array_shift($list);

        if (is_null($result)) {
            return $result;
        } else {
            return $this->processResult($result, false);
        }
    }

    public function findOrEmpty()
    {
        $result = $this->find();
        if ($this->isFetch()) return $result;
        $result = is_null($result) ? $this->resultToModel(null, true) : $result;
        return $result;
    }

    public function findOrFail()
    {
        $result = $this->find();
        if ($this->isFetch()) return $result;
        if (is_null($result)) throw new Exception('Data not found');
        return $result;
    }

    public function value(string $field)
    {
        $result = $this->findOrEmpty();
        if ($this->isFetch()) return $result;
        return $result[$field] ?? null;
    }

    public function column($field, string $key = '')
    {
        $this->field($field, $key);
        $list = $this->connector->execute($this, 'select');

        if ($this->isFetch()) return $list;

        if (is_array($field) && in_array('*', $field)) {
            $field = '*';
        }

        $result = [];
        foreach ($list as $k => $v) {

            $_key = $key ? $v[$key] : $k;

            if ($field == '*') {

                $val = $v;
            } else if (is_array($field)) {

                $val = array_filter(
                    $v,
                    function ($i) use ($field) {
                        return in_array($i, $field);
                    },
                    ARRAY_FILTER_USE_KEY
                );
            } else if (is_string($field)) {

                $val = $v[$field] ?? null;
            }
            $result[$_key] = $val;
        }

        return $result;
    }

    public function chunk($size, $fn)
    {
        if ($this->isFetch()) return $this->select();

        $offset = 0;
        $length = 0;
        $page = 0;
        $isDone = false;

        $limit = $this->getOptions('limit', []);
        if (count($limit) == 2) {
            $offset = $limit[0];
            $length = $limit[1];
        }

        while (!$isDone) {
            $page++;
            $len = $page * $size;

            if ($length && $len >= $length) {
                $len = $length - ($len - $size);
                $isDone = true;
            } else {
                $len = $size;
            }

            $this->limit($offset, $len);
            $list = $this->connector->execute($this, 'select');
            if (empty($list)) $isDone = true;
            $offset = $offset + $len;

            $fn($this->processResult($list, true));
        }
    }

    public function insert(?array $data = null)
    {
        if (empty($data) == false) {
            $this->data($data, true);
        }
        return $this->connector->execute($this, 'insert');
    }

    public function insertGetId(array $data = [])
    {
        $this->data($data, true);
        return $this->connector->execute($this, 'insertGetId');
    }

    public function insertAll(array $data = [])
    {
        $this->data($data, true);
        return $this->connector->execute($this, 'insertAll');
    }

    public function save(array $data = [], bool $forceInsert = false)
    {
        if ($forceInsert) {
            return $this->insert($data);
        }

        $pk = $this->pk();
        $isUpdate = false;
        if (empty($this->getOptions('where')) == false) {
            $isUpdate = true;
        }

        $data = array_merge($this->getOptions('data', []), $data);

        // $pk is string
        if (is_string($pk) && isset($data[$pk])) {
            $this->where($pk, $data[$pk]);
            $isUpdate = true;
            unset($data[$pk]);
        }
        // $pk is null
        else if (is_null($pk)) {
            // just do nothing
        }
        // $pk is array
        else if (is_array($pk)) {
            $isUpdate = true;
            foreach ($pk as $field) {
                if (isset($data[$field])) {
                    $this->where($field, $data[$field]);
                    unset($data[$field]);
                } else {
                    throw new Exception('need composite primary key: ' . $field);
                }
            }
        }

        $this->data($data);

        return $isUpdate ? $this->update($data) : $this->insert($data);
    }

    public function update(array $data = [])
    {
        $this->data($data, true);
        return $this->connector->execute($this, 'update');
    }

    public function inc(string $field, int $step = 1)
    {
        if ($step == 0) return true;
        if ($step < 0) {
            $step = (string) $step;
        } else {
            $step = '+' . $step;
        }
        return $this->update([$field => Db::raw($field . $step)]);
    }

    public function dec(string $field, int $step = 1)
    {
        return $this->inc($field, -1 * $step);
    }

    public function delete()
    {
        if ($this->model) {
            $pk = $this->model->getPk();
            $val = $this->model->origin($pk);
            if ($pk && !is_null($val)) $this->where($pk, $val);
        }
        $where = $this->getOptions('where');
        if (empty($where)) {
            throw new Exception('no condition for delete');
            return;
        }
        return $this->connector->execute($this, 'delete');
    }

    public function query(string $sql, array $bindData = [])
    {
        $this->setOption('sql', $sql);
        $this->mergeBindData($bindData, true);
        return $this->connector->execute($this, 'query');
    }

    public function execute(string $sql, array $bindData = [])
    {
        $this->setOption('sql', $sql);
        $this->mergeBindData($bindData, true);
        return $this->connector->execute($this, 'execute');
    }

    public function buildSql($method = 'select')
    {
        $this->fetchSql();
        $sql = $this->connector->$method($this);
        return '(' . $sql . ')';
    }

    public function debug($method = 'select', $handle = 'dump')
    {
        $res = $this->connector->debug($this, $method);
        if ($handle == 'return') return $res;
        if ($handle == 'dump') dump($res);
        if ($handle == 'halt') halt($res);
    }
}
